#include "WindowTools.h"

WindowTools* WindowTools::instance;

WindowTools::WindowTools(HINSTANCE hInstance)
{
    instance = this;
	this->hInstance = hInstance;
	
	menuHeight = GetSystemMetrics(SM_CYMENU);
	captionHeight = GetSystemMetrics(SM_CYCAPTION);
	frameHeight = GetSystemMetrics(SM_CYFRAME);
	smallCaptionHeight = GetSystemMetrics(SM_CYSMCAPTION);
	minHeight = captionHeight + frameHeight * 2;
	minSmallHeight = smallCaptionHeight + frameHeight * 2;
}

WindowTools::~WindowTools()
{
	Deactivate();		
}

WindowTools* WindowTools::CreateInstance(HINSTANCE hInstance)
{
    if (instance)
        return 0;

    return new WindowTools(hInstance);
}

CCState WindowTools::Activate()
{
    CCState result;
    if ((result = CassiopeiaComponent::Activate()) != CCState::Successful)
        return result;

	RecoverWindowsInCaseOfPreviousCrash();

	mouseHookActive = false;

	hWinEventHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_OBJECT_LOCATIONCHANGE, 0, WinEventProc, 0, 0, 0);
	hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, hInstance, 0);

    return CCState::Successful;
}

CCState WindowTools::Deactivate()
{
    CCState result;
    if ((result = CassiopeiaComponent::Deactivate()) != CCState::Successful)
        return result;

	UnhookWinEvent(hWinEventHook);
	UnhookWindowsHookEx(hMouseHook);

	ResetReducedWindows();

    return CCState::Successful;
}




void WindowTools::Add(HWND hWnd)
{
	if (Exists(hWnd))
		return;

	ReducedData data={0};
	windowsForReducing[hWnd] = data;
	SetProp(hWnd, TEXT("Cassiopeia_ReducedWindow"), (HANDLE)1);	
}

void WindowTools::Remove(HWND hWnd)
{
	if (!Exists(hWnd))
		return; 

	windowsForReducing.erase(hWnd);
	RemoveProp(hWnd, TEXT("Cassiopeia_ReducedWindow"));
}

bool WindowTools::Exists(HWND hWnd)
{
	return (windowsForReducing.find(hWnd) != windowsForReducing.end());
}


void WindowTools::RecoverWindowsInCaseOfPreviousCrash()
{
	//if Cassiopeia crashed, make all reduced windows resizable
	EnumWindows(EnumWindowsProc, 0);
}

void WindowTools::ResetReducedWindows()
{
	map<HWND,ReducedData>::iterator iter;
	for (iter = windowsForReducing.begin();iter!=windowsForReducing.end();iter++)
	{
		if (iter->second.Size.Height)
		{
			RestoreReducedWindow(iter->first);
			RemoveProp(iter->first, TEXT("Cassiopeia_ReducedWindow"));
		}
	}

	windowsForReducing.clear();
}

void WindowTools::ReduceWindowsExceptOne(HWND hWnd)
{
	map<HWND,ReducedData>::iterator iter;
	for (iter = windowsForReducing.begin();iter!=windowsForReducing.end();iter++)
	{
		if (iter->second.Size.Height==0
			&& GetParent(hWnd) != iter->first
			&& GetWindow(hWnd, GW_OWNER) != iter->first
			&& hWnd != iter->first)
			ReduceWindow(iter->first);
	}
}

void WindowTools::RestoreReducedWindow(HWND hWnd)
{
	//"iterate" through all possible windows (hWnd, parent, owner ->in exactly this order)
	HWND parent = GetParent(hWnd);
	HWND owner = GetWindow(hWnd, GW_OWNER);

	if (RestoreReducedWindowInternal(hWnd))
		return;
	else if (RestoreReducedWindowInternal(parent))
		return;

	RestoreReducedWindowInternal(owner);
}

bool WindowTools::RestoreReducedWindowInternal(HWND hWnd)
{
	if (Exists(hWnd) && IsWindow(hWnd) && (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_CAPTION) == WS_CAPTION && windowsForReducing[hWnd].Size.Height)
	{
		if (!IsIconic(hWnd))
		{
			SetWindowPos(hWnd, 0, 0, 0, windowsForReducing[hWnd].Size.Width, windowsForReducing[hWnd].Size.Height, SWP_NOMOVE | SWP_NOACTIVATE |  SWP_NOSENDCHANGING);
			InvalidateRgn(hWnd, 0, TRUE);
		}
		else
		{
			WINDOWPLACEMENT wp;
			wp.length = sizeof(WINDOWPLACEMENT);
			GetWindowPlacement(hWnd, &wp);
			wp.rcNormalPosition.right = wp.rcNormalPosition.left + windowsForReducing[hWnd].Size.Width;
			wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + windowsForReducing[hWnd].Size.Height;
			SetWindowPlacement(hWnd, &wp);
		}

		if (windowsForReducing[hWnd].IsResizable)
			SetWindowLongPtr(hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) | WS_THICKFRAME);
		
		windowsForReducing[hWnd].Size.Height = 0;

		return true;
	}

	return false;
}

void WindowTools::ReduceWindow(HWND hWnd)
{
	if (IsWindow(hWnd) && (GetWindowLongPtr(hWnd,GWL_STYLE) & WS_CAPTION) == WS_CAPTION && Exists(hWnd) && !windowsForReducing[hWnd].Size.Height)
	{
		RECT windowRect = Utilities::CalcWindowRect(hWnd);

		RECT clientRect;
		GetClientRect(hWnd,&clientRect);

		TCHAR className[255];
		memset(className,0,sizeof(className));
		GetClassName(hWnd,className,sizeof(className));

		windowsForReducing[hWnd].IsResizable = (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_THICKFRAME) == WS_THICKFRAME;

		windowsForReducing[hWnd].Size.Height = windowRect.bottom - windowRect.top;
		windowsForReducing[hWnd].Size.Width = windowRect.right - windowRect.left;

		int height=(windowRect.bottom-windowRect.top)-(clientRect.bottom-clientRect.top);

		if (GetMenu(hWnd))
			height-=menuHeight;

		if (!_tcscmp(className,TEXT("CabinetWClass")))
		{
			HWND workerW = FindWindowEx(hWnd,0,TEXT("WorkerW"),0);
			if (workerW)
			{
				RECT workerWRect;
				GetWindowRect(workerW,&workerWRect);
				height+=(workerWRect.bottom-workerWRect.top);
			}
		}

		if ((GetWindowLongPtr(hWnd,GWL_EXSTYLE) & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW)
		{
			if (height < minSmallHeight)
				height = minSmallHeight;
		}
		else
		{
			if (height < minHeight)
				height = minHeight;
		}

		SetWindowPos(hWnd,0,0,0,windowRect.right-windowRect.left,height,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOSENDCHANGING);

		SetWindowLongPtr(hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) & ~WS_THICKFRAME);
	}
}


VOID CALLBACK WindowTools::WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hWnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime)
{
	if ((dwEvent == EVENT_SYSTEM_FOREGROUND || dwEvent == EVENT_SYSTEM_MINIMIZEEND) && idObject == OBJID_WINDOW)
	{
		if (!IsIconic(hWnd))
		{
			instance->ReduceWindowsExceptOne(Utilities::TopLevelWindow(hWnd));
			instance->RestoreReducedWindow(Utilities::TopLevelWindow(hWnd));
		}
	}
	else if (dwEvent == EVENT_OBJECT_DESTROY && idObject == OBJID_WINDOW)
	{
		if (instance->Exists(hWnd))
			instance->Remove(hWnd);

	}
	else if (dwEvent == EVENT_OBJECT_LOCATIONCHANGE && idObject == OBJID_CURSOR)
	{	
		/*HWND h = Utilities::TopLevelWindowFromMousePosition();

		instance->RestoreReducedWindow(h);
		instance->ReduceWindowsExceptOne(h, GetForegroundWindow());
*/



		//----mouse hook backup----
		static int mouseMoves = 0;

		if (mouseMoves > 15)
		{
			if (!instance->mouseHookActive)
			{
				UnhookWindowsHookEx(instance->hMouseHook);
				instance->hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, instance->hInstance, 0);
			}

			instance->mouseHookActive = false;
			mouseMoves = 0;
		}

		mouseMoves++;
	}
}




LRESULT CALLBACK WindowTools::MouseHookProc(int code, WPARAM wParam, LPARAM lParam)
{
	bool cancel = false;

	if (wParam == WM_MOUSEMOVE)
	{	
		instance->mouseHookActive = true;
	}
	else if (wParam == WM_LBUTTONUP)
	{
		HWND hWnd = Utilities::TopLevelWindowFromMousePosition();

		if (!Utilities::IsSpecialWindow(hWnd) && !Utilities::IsSpecialCassiopeiaWindow(hWnd))
		{
			TITLEBARINFO tti;
			tti.cbSize = sizeof(TITLEBARINFO);
			GetTitleBarInfo(hWnd, &tti);

			MSLLHOOKSTRUCT* msllhs = (MSLLHOOKSTRUCT*)lParam;

			if ((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000
				&& Utilities::RectContainsPoint(tti.rcTitleBar, msllhs->pt))
			{
				if (Utilities::IsTopMost(hWnd))
					Utilities::SetNoTopMost(hWnd);
				else
					Utilities::SetTopMost(hWnd);
			}
		}
	}
	else if (wParam == WM_MBUTTONUP)
	{
		HWND hWnd = Utilities::TopLevelWindowFromMousePosition();

		if (!Utilities::IsSpecialWindow(hWnd) && !Utilities::IsSpecialCassiopeiaWindow(hWnd))
		{
			TITLEBARINFO tti;
			tti.cbSize = sizeof(TITLEBARINFO);
			GetTitleBarInfo(hWnd, &tti);

			MSLLHOOKSTRUCT* msllhs = (MSLLHOOKSTRUCT*)lParam;

			if ((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000
				&& Utilities::RectContainsPoint(tti.rcTitleBar, msllhs->pt))
			{
				if (!instance->Exists(hWnd))
					instance->Add(hWnd);
				else
					instance->Remove(hWnd);
			}
		}
	}

	return (cancel)?1L : CallNextHookEx(instance->hMouseHook, code, wParam, lParam);
}

BOOL CALLBACK WindowTools::EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
	if (IsWindowVisible(hWnd) && GetProp(hWnd, TEXT("Cassiopeia_ReducedWindow")))
	{
		SetWindowLongPtr(hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) | WS_THICKFRAME);
		RemoveProp(hWnd, TEXT("Cassiopeia_ReducedWindow"));
	}

	return TRUE;
}
